/*fl2440_key.c*/

/*按键驱动程序*/

/*fl2440所用到的按键资源*/
/**************************************************/
/* 按键          对应的IO寄存器     对应的中断引脚	 */
/* S2             GPF0                 EINT0     */
/* S3             GPF2                 EINT2     */
/* S4             GPF3                 EINT3     */
/* S5             GPF4                 EINT4     */
/**************************************************/

/*要搞清楚谁是输入*/
/*在这里,按键控制对应的中断引脚,从而控制对应的IO寄存器*/

/*引用的头文件*/
#include <linux/module.h> 		/*模块有关的*/
#include <linux/kernel.h> 		/*内核有关的*/
#include <linux/fs.h> 			/*文件系统有关的*/
#include <linux/init.h> 		/*init*/
#include <linux/delay.h> 		/*delay*/
#include <linux/poll.h> 		/*poll*/
#include <asm/irq.h> 			/*中断*/
#include <linux/interrupt.h> 	/*linux中断*/
#include <asm/uaccess.h> 		/*uaccess*/
#include <asm/arch/regs-gpio.h> /*寄存器设置*/
#include <asm/hardware.h> 		/*hardware*/

/*定义宏*/
#define KEY_MAJOR 230 			/*主设备号*/
#define DEVICE_NAME "fl2440_key" 	/*设备名*/

/*定义按钮中断的描述结构体*/
struct key_irq_desc
{
	int irq; /*中断号*/

	int pin; /*中断的引脚,该引脚的电平由按键来控制*/

	int pin_setting; /*中断控制的寄存器*/

	int number; /*编号*/
	char *name; /*名称*/
};

/*指定6个按键的信息*/
static struct key_irq_desc key_irqs [] =
{
	{IRQ_EINT0,S3C2410_GPF0,S3C2410_GPF0_EINT0,0,"S2"}, /*S2*/
	{IRQ_EINT2,S3C2410_GPF2,S3C2410_GPF2_EINT2,1,"S3"}, /*S3*/
	{IRQ_EINT3,S3C2410_GPF3,S3C2410_GPF3_EINT3,2,"S4"}, /*S4*/
	{IRQ_EINT4,S3C2410_GPF4,S3C2410_GPF4_EINT4,3,"S5"}, /*S5*/
};

/*key_values数组*/
/*存放各个按键在发生中断情况下的值*/
/*这个数组是我们存放按键操作结果的,因此非常重要*/
static volatile int key_values[] = {0,0,0,0,0,0};

/*宏DECLARE_WAIT_QUEUE_HEAD(),是干什么的呢?*/
/*该宏应该是创建了一个等待队列*/
/*等待队列,是进程调度的一种重要方法*/
/*等待队列也很有意思,key_waitq,表示按键等待的队列*/
/*就是说,按键一按下,就会激活其等待队列里的进程,来做相应的处理*/
/*因此,按键的等待队列,或者说中断所设置的等待队列,*/
/*是中断处理中非常重要的资源,它大大扩展了中断处理的能力*/
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);

/*key_values数组中是否有数据的标志,0表示无数据可读,1表示有数据可读*/
static volatile int ev_press = 0; /*初始为0*/

/*参数irq---中断号*/
/*中断服务程序应该是与中断号一一对应的*/
/*对应于某个中断号的中断一发生,就会调用该中断号对应的服务程序*/
/*那么,检测中断的发生,就成了先决条件*/
/*参数dev_id ---具体是哪一个按钮*/
static irqreturn_t keys_interrupt(int irq,void *dev_id);

/*fl2440_keys_open()函数申明*/
/*驱动函数open调用的具体函数*/
/*由open函数具体实现硬件的初始化工作*/
/*以及软件的初始化工作*/
/*为我们的键盘设备的运行创造好环境*/
static int fl2440_keys_open(struct inode *inode,struct file *file);

/*fl2440_keys_close()函数的申明*/
/*release调用的具体函数*/
/*设备软件环境的拆卸*/
/*具体就是中断的释放工作*/
/*因为中断资源,也是系统宝贵的资源,所以不用的时候,要释放*/
static int fl2440_keys_close(struct inode *inode,struct file *file);

/*fl2440_keys_read()函数的申明*/
/*read调用的具体函数*/
/*由它读取键盘输入的结果*/
/*实质上就是读取key_values数组的值*/
/*它完成了键盘作为输入设备的核心功能*/
/*数组是否可读,要根据标志位ev_press来判断*/
/*如果数组可读,则读取数据到用户buffer中*/
/*如果数组不可读,则进程进入等待队列,等待到数组可读为止*/
/*等待队列机制,是中断管理中常用到的机制*/
/*因为有些进程经常需要等待某一事件的发生*/
/*注意__user,指的是用户空间*/
/*即要把键盘的输入结果读取到用户空间去*/
static int fl2440_keys_read(struct file *filp,char __user *buff,size_t count,loff_t *offp);

/*fl2440_keys_poll()函数的申明*/
/*poll调用的具体函数*/
/*poll实质上是select的调用函数*/
/*如果有按键数据,则select会立刻返回*/
/*如果没有按键数据,则等待*/
/*实质上这是键盘等待输入的机制*/
static unsigned int fl2440_keys_poll(struct file *file,struct poll_table_struct *wait);

/*file_operations结构体*/
/*驱动函数的设置*/
/*分别将前面的驱动函数设置进来*/
static struct file_operations fl2440_keys_fops =
{
	.owner = THIS_MODULE,
	.open = fl2440_keys_open, /*open()*/
	.release = fl2440_keys_close, /*release()*/
	.read = fl2440_keys_read, /*read()*/
	.poll = fl2440_keys_poll /*poll()*/
};

/*fl2440_keys_init()函数的申明*/
/*module_init调用的具体函数*/
/*模块创建时的初始化函数*/
/*主要做的工作是注册设备和创建设备*/
/*而具体的硬件初始化工作,它可以不做*/
/*而把它留给fops里的函数来做*/
static int __init fl2440_keys_init(void);

/*模块卸载时的扫尾工作*/
/*主要是设备的卸载工作*/
static void __exit fl2440_keys_exit(void);

module_init(fl2440_keys_init);/*模块创建时的入口点*/
module_exit(fl2440_keys_exit);/*模块卸载时的入口点*/

/********************************************************************/
/*********************下面是前面申明函数的实现***********************/
/********************************************************************/

/**********************fl2440_keys_init()***********************/

static int __init fl2440_keys_init(void)
{
	int ret; /*设备注册的返回值*/

	/*注册设备驱动程序*/
	/*设备号,设备名,和驱动函数*/
	ret = register_chrdev(KEY_MAJOR,DEVICE_NAME,&fl2440_keys_fops);

	/*对注册失败的处理*/
	if(ret < 0)
	{
		printk(DEVICE_NAME " can't register major number\n");
		return ret;
	}

	/*创建设备*/
	/*devfs_mk_cdev()函数是内核态的设备创建函数*/
	/*而mknod是用户态的设备创建函数*/
	devfs_mk_cdev(MKDEV(KEY_MAJOR,0),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP,DEVICE_NAME);
	printk(DEVICE_NAME " initialized\n");
	return 0;
}


/******************fl2440_keys_exit()****************************/

static void __exit fl2440_keys_exit(void)
{
	/*移除设备*/
	devfs_remove(DEVICE_NAME);

	/*注消设备驱动*/
	unregister_chrdev(KEY_MAJOR,DEVICE_NAME);
}


/*****************fl2440_keys_open()******************************/

static int fl2440_keys_open(struct inode *inode,struct file *file)
{
int i;    /*循环变量,因为有6个按钮*/
int err; /*中断注册函数的返回值*/

/*对每个按钮分别处理,用for循环来做*/
/*具体地是要联结寄存器和相应的引脚*/
/*联结中断号和相应的中断服务程序*/
/*这一步类似于前面所说的驱动的注册*/
/*我们可以成功称作中断的注册*/
for(i = 0;i < sizeof(key_irqs)/sizeof(key_irqs[0]);i++)
{
	/*寄存器与中断引脚的联结*/
   	s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting);

   	/*中断的注册*/
	/*request_irq()函数*/
	/*要注意其输入参数*/
	/*&key_irqs[i]是该中断享有的资源*/
	/*会被传入keys_interrupt,进行处理*/
   	err = request_irq(key_irqs[i].irq,keys_interrupt,NULL,key_irqs[i].name,(void *)&key_irqs[i]);

   	/*中断类型的设置*/
	/*set_irq_type()函数*/
	/*IRQT_BOTHEDGE的中断类型代表什么样的中断呢?*/

	/*有几个非常重要的问题*/
	/*中断注册后,并设置好其中断类型之后,当有中断发生时,*/
	/*即按下某个按钮时,系统能够自动检测到有中断发生吗?*/
	/*检测到有中断发生,它能够自动辨别是几号中断吗?*/
	/*知道了是几号中断,那么它能自动调用其中断服务程序吗?*/
	/*对这几个问题的解答,够成了linux系统中断处理机制的核心*/
   	set_irq_type(key_irqs[i].irq,IRQT_RISING);

   	/*注册失败的处理*/

   	if(err)
		break; /*跳出循环*/
	}

	/*若有一个按钮中断注册失败*/
	/*则还需把前面注册成功的中断给拆了*/

	if(err)
	{
		i--; /*回到前面一个按钮的处理*/

	   	for(;i >=0; i--) /*依此拆除*/
		{
			/*使中断不起作用*/
			disable_irq(key_irqs[i].irq);

			/*释放中断资源*/
			free_irq(key_irqs[i].irq,(void *)&key_irqs[i]);
		}

	   return -EBUSY; /*中断注册没成功的最终的返回值*/
	}

	return 0; /*正常返回*/
}


/**************************keys_interrupt()*****************************/
/*此中断服务程序,在每中断一次,就要对key_values数组设一下值*/
/*并对数组可读标志位ev_press设一下值*/
/*并唤醒在等待队列里的进程*/
/*这是中断处理经常要做的事情*/
/*在这里,等待队列key_waitq里经常等待的进程是数组的读取进程*/
/*就是说,读取进程在没有读到数据的时候就一直在等待,等待按键的输入*/
/*读取进程在等待,并不代表所有进程在等待,其它进程该干啥干啥去*/

static irqreturn_t keys_interrupt(int irq,void *dev_id)
{
	/*key_irq_desc结构体变量*/
	/*对传入的资源进行处理*/

	struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id;

	/*获取寄存器的值*/
	/*这一步至关重要*/
	/*s3c2410_gpio_getpin()函数直接获取寄存器的值*/

	/*要注意,按一下按钮，会发生两次中断*/

	/*即按下是一次中断，放开又是一次中断*/

	int up = s3c2410_gpio_getpin(key_irqs->pin);

	/*通过电路原理图,可以知道没按下的时候,中断引脚应该是高电平*/
	/*从而寄存器的值应该是1*/
	/*变量取up也是有意义的,表示默认状态是弹起的状态*/
	/*当按下按钮的状态下,寄存器的值就应该是0*/

	/*下面对up的值进行处理*/
	/*即是要把数据经过一定的变换存入key_values数组中*/

	if(up)   /*如果是弹起的状态*/
	/*那么就要在key_values数组的相应位存入很大的一个值*/
	/*同时又要能从值里辨别出是哪个按键*/
	   key_values[key_irqs->number] = (key_irqs->number + 1) + 0x80;
	/*比如K1键开启的状态下,key_values[0]被置为(0+1)+0x80,即为129*/
	else /*如果按键是闭合的状态*/
	/*那么就要在key_values数组的相应位存入一个很小的数*/
	/*同时又要能从值中辨别出是哪个键*/

	   key_values[key_irqs->number] = (key_irqs->number + 1);
	/*比如K1键闭合,则key_values[0]被置为(0+1),即为1*/
	/*对数组可读标志位进行设置*/

	ev_press = 1; /*表示数组已经可读了*/
	/*唤醒休眠的进程?*/
	/*key_waitq队列里存放有相应的处理进程*/
	/*如读取数组的值的进程*/
	/*要注意wake_up_interruptible()这些函数的用法*/
	wake_up_interruptible(&key_waitq);
	/*返回*/
	return IRQ_RETVAL(IRQ_HANDLED); /*?*/
}

/**********************fl2440_keys_close()*****************************/

static int fl2440_keys_close(struct inode *inode,struct file *file)
{
	int i; /*循环变量,要操作好几个按键*/

	/*for循环,对各个按键依此释放中断*/

	for(i = 0;i < sizeof(key_irqs)/sizeof(key_irqs[0]);i++)
	{
	/*使中断失效*/

	   disable_irq(key_irqs[i].irq);

	   /*释放资源*/

	   free_irq(key_irqs[i].irq,(void *)&key_irqs[i]);
	}

	/*返回*/

	return 0;
}


/**********************fl2440_keys_read()***************************/
/*要注意,该read函数,只读取一次中断的值,而不是连续地读入*/
/*要做到连续地读入,则需要做一个循环,不断地调用该read函数,但那不是驱动程序里该做的事情*/

static int fl2440_keys_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
	unsigned long err;   /*copy_to_user()函数的返回值*/

	/*如果key_values 数组里没有值,则会此进程会休眠*/
	/*一直到中断来临之后,中断服务程序会唤醒此休眠进程从而继续读取值*/
	/*key_values数组里有没有值,是靠ev_press标志位来判断的*/
	/*有值,就是1,无值,就是0*/

	/*进程等待队列的机制,是进程调度的一种方法*/

	if(!ev_press) /*标志位为0,即无数据时*/
	{
		if(filp->f_flags & O_NONBLOCK) /*??*/
		return -EAGAIN;
		else /*进程休眠,放进key_waitq等待队列*/
		/*这里,把ev_press标志位设成了休眠进程的标志位了?*/
		/*这是为了便于利用poll_wait函数*/
		/*也就是利于select函数*/
		wait_event_interruptible(key_waitq,ev_press);
		/*在中断处理函数中,此进程会被唤醒*/
		/*唤醒前,ev_press 已被置1了*/
		/*唤醒后的执行点从这里开始*/
	}

	/*下面就是标志位为1,即有数据可读的的处理情况*/
	/*开始往用户空间读数据*/
	err = copy_to_user(buff,(const void *)key_values,min(sizeof(key_values),count));

	/*对key_values数组清零*/
	memset((void *)key_values,0,sizeof(key_values));

	/*标志位置0*/
	/*表示读取过了*/
	ev_press = 0;

	if(err) /*读取错误*/
		return -EFAULT;
	else /*读取正确*/
	return min(sizeof(key_values),count);/*则返回读取到的字节数*/
}

/************************fl2440_keys_poll()***********************/

static unsigned int fl2440_keys_poll(struct file *file,struct poll_table_struct *wait)
{
	unsigned int mask = 0; /* */

	/*poll_wait()函数*/
	/*会监测进程队列key_waitq里的进程*/
	/*例如,如果fl2440_key_read所在的进程的标志位ev_press置为1了*/
	/*那么就不会再等待了*/
	/*这实质上就是select函数的运行机制*/
	poll_wait(file,&key_waitq,wait);

	if(ev_press)
		mask |= POLLIN | POLLRDNORM; /*??*/

	return mask;
}

/*驱动程序的一些信息*/
MODULE_AUTHOR("wangyijieonline@126.com"); /*驱动程序的作者*/
MODULE_DESCRIPTION("S3C2410/S3C2440 key Driver"); /*描述信息*/
MODULE_LICENSE("GPL"); /*遵循的协议*/

